package uk.co.accolade.clash.core;

import static uk.co.accolade.clash.core.UserChanges.userChanges;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ChangesRegistry {

    private Username localUsername;
    private Map<Username, UserChanges> allUsersChanges = new HashMap<Username, UserChanges>();
    private ClashHandlerRegistry clashHandlerRegistry = new ClashHandlerRegistry();


    public ChangesRegistry( Username localUsername ) {
        this.localUsername = localUsername;
    }

    public void registerClashHandler( ClashHandler clashHandler ) {
        clashHandlerRegistry.registerClashHandler( clashHandler );
    }

    public void handleChanges(UserChanges changes, ChangeHandler handler) {
        handler.handleChanges( changes );
    }

    public boolean changesAreLocal( UserChanges changes ) {
        return localUsername.equals( changes.getUsername() );
    }

    void applyChanges( UserChanges changes ) {
        clearClashesForRemovedChanges( changes );
        handleClashesForNewChanges( changes );
        allUsersChanges.put(changes.getUsername(), changes);
    }

    private void clearClashesForRemovedChanges( UserChanges changes ) {
        List<UserChanges> changesToClear = getRemovedClashes(changes);
        clashHandlerRegistry.clearClashes(changesToClear);
    }

    private void handleClashesForNewChanges( UserChanges changes ) {
        List<UserChanges> newClashes = getNewClashes(changes);
        clashHandlerRegistry.handleClashes(newClashes);
    }

    List<UserChanges> getNewClashes( UserChanges changes ) {
        UserChangesDiff changesDiff = diff( changes );
        return intersectionsOtherUsers(changesDiff.getAddedChanges());
    }

    List<UserChanges> getRemovedClashes( UserChanges changes ) {
        UserChangesDiff changesDiff = diff( changes );
        return intersectionsOtherUsers(changesDiff.getRemovedChanges());
    }


    List<UserChanges> intersectionsOtherUsers( UserChanges changes ) {
        List<UserChanges> intersections = new ArrayList<UserChanges>();
        for( Username otherUser : allUsersChanges.keySet() ) {
            if( !otherUser.equals( changes.getUsername() )
                    && onlyCheckLocalUser(changes.getUsername(), otherUser)) {
                UserChanges otherUserChanges = getChangesFor( otherUser );                
                Username userForResults = getOtherUsername(changes, otherUserChanges);
                intersections.add(changes.intersect( otherUserChanges, userForResults ));
            }
        }
        return intersections;
    }

    private boolean onlyCheckLocalUser( Username firstUsername, Username secondUsername ) {
        return firstUsername.equals( localUsername ) || secondUsername.equals( localUsername );
    }

    Username getOtherUsername( UserChanges firstChanges, UserChanges secondChanges ) {
        if( firstChanges.getUsername().equals( localUsername )) {
            return secondChanges.getUsername();
        } else if( secondChanges.getUsername().equals( localUsername )) {
            return firstChanges.getUsername();
        }
        throw new IllegalArgumentException("Only check changes against the local user.");
    }

    UserChangesDiff diff( UserChanges newChanges ) {
        UserChanges oldChanges = getChangesFor( newChanges.getUsername() );
        return UserChangesDiff.diff( oldChanges, newChanges );
    }

    private UserChanges getChangesFor( Username username ) {
        UserChanges changes = allUsersChanges.get( username );
        return (changes == null) ? userChanges( username ) : changes;
    }
}